/* ARP parser and response code */

/* Copyright (C) 2009-2011 David Zanetti
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; version 2 of the License.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License along
 *  with this program; if not, write to the Free Software Foundation, Inc.,
 *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */


#include <stdio.h>
#include <avr/pgmspace.h>
#include <avr/io.h>
#include <avr/eeprom.h>
#include <string.h>
#include <util/atomic.h>

#include "global.h"
#include <util/delay.h>

#include "arp.h"
#include "serial.h"
#include "encx24j600.h"
#include "rtc.h"
#include "ether.h"
#include "ip.h"

#define ARP_TABLE_MAX 16
#define ARP_AGE 300 /* entries are *always* deleted after 300s */
#define ARP_PENDING 2 /* how many seconds we allow before purging unsent packets */

#define ARP_REQ 0x01
#define ARP_REP 0x02

typedef struct {
		uint8_t hardware[2];
		uint8_t protocol[2];
		uint8_t hardware_len;
		uint8_t protocol_len;
		uint8_t opcode[2];
		uint8_t hardware_sender[6];
		uint8_t protocol_sender[4];
		uint8_t hardware_target[6];
		uint8_t protocol_target[4];
} arp_ip_pkt_t;

typedef struct {
		uint8_t addr[4]; /* IP address */
		uint8_t mac[6]; /* Ethernet MAC */
		uint8_t slot; /* pending TX slot+1 */
		uint32_t age; /* when was this entry last touched. note: does not apply to replies automac'd */
} arp_table_entry_t;

/* ARP Table */
arp_table_entry_t arp_table[ARP_TABLE_MAX];

/* Process arp. Returns size of payload written out */

//uint16_t arp_process(uint16_t txstart) {
uint8_t arp_process(uint8_t slot) {
	arp_ip_pkt_t pkt;

	/* collect some more bytes from the packet to fill our arp structure */
	if (encx24j600_rx(&pkt,sizeof(arp_ip_pkt_t)) != sizeof(arp_ip_pkt_t)) {
		serial0_tx_PGM(PSTR("arp: short frame, discarding\r\n"));
		return 0;
	}

#ifdef DEBUG_ARP
	serial0_tx_PGM(PSTR("arp: hw "));
	serial0_tx_hex(pkt.hardware[0]);
	serial0_tx_hex(pkt.hardware[1]);
	serial0_tx_PGM(PSTR(" proto "));
	serial0_tx_hex(pkt.protocol[0]);
	serial0_tx_hex(pkt.protocol[1]);
	if (pkt.opcode[0] == 0x0 && pkt.opcode[1] == ARP_REQ) {
		serial0_tx_PGM(PSTR(" request "));
	} else {
		serial0_tx_PGM(PSTR(" unknown "));
	}
	serial0_tx_PGM(PSTR("target "));
	{
		uint8_t n;
		for (n = 0; n < 4; n++) {
			serial0_tx_dec(pkt.protocol_target[n]);
			if (n != 3) {
				serial0_tx_cout('.');
			}
		}
	}
	serial0_tx_PGM(PSTR(" sender "));
	{
		uint8_t n;
		for (n = 0; n < 4; n++) {
			serial0_tx_dec(pkt.protocol_sender[n]);
			if (n != 3) {
				serial0_tx_cout('.');
			}
		}
	}
	serial0_tx_cr();
#endif

	/* now consider if this arp is for us */
	if (pkt.hardware[0] == 0x0 &&
			pkt.hardware[1] == 0x1 && /* Ethernet */
			pkt.protocol[0] == 0x8 &&
			pkt.protocol[1] == 0x0 && /* IP */
			pkt.opcode[0] == 0x0 &&
			pkt.opcode[1] == ARP_REQ && /* arp request */
			pkt.protocol_target[0] == ip_addr[0] &&
			pkt.protocol_target[1] == ip_addr[1] &&
			pkt.protocol_target[2] == ip_addr[2] &&
			pkt.protocol_target[3] == ip_addr[3]) { /* is for our IP address */
		/* generate a reply ARP packet */
		{
			arp_ip_pkt_t reply;
			uint8_t n;

			reply.hardware[0] = 0x0;
			reply.hardware[1] = 0x1; /* Ethernet */
			reply.protocol[0] = 0x8;
			reply.protocol[1] = 0x0; /* IP */
			reply.hardware_len = 6; /* Ethernet MAC length */
			reply.protocol_len = 4; /* IP address length */
			reply.opcode[0] = 0x0;
			reply.opcode[1] = ARP_REP; /* This is a reply */
			for (n = 0; n < 4; n++) { /* copy IP addresses */
				reply.protocol_target[n] = pkt.protocol_sender[n];
				reply.protocol_sender[n] = ip_addr[n];
			}
			for (n = 0; n < 6; n++) { /* copy MAC addresses */
				reply.hardware_sender[n] = eth_mac[n];
				reply.hardware_target[n] = pkt.hardware_sender[n];
			}
			/* packet is ready, append it, and indicate we did */
			/* we don't care about address we end up at, this is end of the chain */
			//   n = encx24j600_tx_append(&reply,sizeof(arp_ip_pkt_t));
			n = encx24j600_tx_prepend(slot,(uint8_t *)&reply,sizeof(arp_ip_pkt_t));
#ifdef DEBUG_ARP
			serial0_tx_PGM(PSTR("arp: sending: hw "));
			serial0_tx_hex(reply.hardware[0]);
			serial0_tx_hex(reply.hardware[1]);
			serial0_tx_PGM(PSTR(" proto "));
			serial0_tx_hex(reply.protocol[0]);
			serial0_tx_hex(reply.protocol[1]);
			serial0_tx_PGM(PSTR(" reply target "));
			{
				uint8_t n;
				for (n = 0; n < 4; n++) {
					serial0_tx_dec(reply.protocol_target[n]);
					if (n != 3) {
						serial0_tx_cout('.');
					}
				}
			}
			serial0_tx_PGM(PSTR(" sender "));
			{
				uint8_t n;
				for (n = 0; n < 4; n++) {
					serial0_tx_dec(reply.protocol_sender[n]);
					if (n != 3) {
						serial0_tx_cout('.');
					}
				}
			}
			serial0_tx_cr();
#endif
			return n;
			//   return sizeof(arp_ip_pkt_t);
		}
	}

	/* is this a packet for an address in our table we're expecting? */
	if (pkt.hardware[0] == 0x0 &&
			pkt.hardware[1] == 0x1 && /* Ethernet */
			pkt.protocol[0] == 0x8 &&
			pkt.protocol[1] == 0x0 && /* IP */
			pkt.opcode[0] == 0x0 &&
			pkt.opcode[1] == ARP_REP && /* arp reply */
			pkt.protocol_target[0] == ip_addr[0] &&
			pkt.protocol_target[1] == ip_addr[1] &&
			pkt.protocol_target[2] == ip_addr[2] &&
			pkt.protocol_target[3] == ip_addr[3]) { /* is inteded for us */
		uint8_t n;

#ifdef DEBUG_ARP
		serial0_tx_PGM(PSTR("arp: rx reply for us\r\n"));
#endif
		/* check the protocol_sender to see if we have a table entry for it */
		for (n = 0; n < ARP_TABLE_MAX; n++) {
			if (memcmp(arp_table[n].addr,pkt.protocol_sender,4) == 0) {
				/* yes! this is for us */
				memcpy(arp_table[n].mac,pkt.hardware_sender,6);
				arp_table[n].age = rtc_s; /* touch how old this entry is */
				/* if we have a TX slot, then finish the send */
				if (arp_table[n].slot) {
#ifdef DEBUG_ARP
					serial0_tx_PGM(PSTR("tx waiting packet\r\n"));
#endif
					ether_send((arp_table[n].mac),0x0800,(arp_table[n].slot)-1); /* it's always IP because we're resolving IP addresses! */
					arp_table[n].slot = 0; /* and mark it sent */
					break; /* out of for loop */
				}
			}
		}
	}
	return E_NOTHING; /* technically we maybe did send something, but not in reply to this packet */
}

void arp_init(void) {
	uint8_t n;

#ifdef DEBUG_ARP
	serial0_tx_PGM(PSTR("arp: clearing table\r\n"));
#endif
	/* clear the ARP table */
	for (n = 0; n < ARP_TABLE_MAX; n++) {
		memset(&(arp_table[n]),0,sizeof(arp_table_entry_t));
	}

	/* .. something else I guess? */
	return;
}

uint8_t arp_resolve(uint8_t slot, uint8_t *dst, uint8_t *dst_mac) {
	uint8_t n, low = 0xff;
	uint8_t target_addr[4]; /* the resolved-by-routing target address */

	/* if we have a mask and default gateway, try to use them */
	if (ip_mask[0] != 0 &&
		ip_gateway[0] != 0 &&
		ip_gateway[1] != 0 &&
		ip_gateway[2] != 0 &&
		ip_gateway[3] != 0 ) { /* we have *some* bits set somewhere in mask and a non-zero gateway */
		uint8_t dst_masked[4], local_masked[4];
		/* perform routing on the target address */
#ifdef DEBUG_ARP_ROUTING
		serial0_tx_PGM(PSTR("arp: perform routing\r\n"));
#endif
		for (n = 0; n < 4; n++) {
			dst_masked[n] = dst[n] & ip_mask[n];
			local_masked[n] = ip_addr[n] & ip_mask[n];
		}
		if (memcmp(dst_masked,local_masked,4) != 0) {
			/* we need to use the router instead */
#ifdef DEBUG_ARP_ROUTING
			serial0_tx_PGM(PSTR("arp: using gw\r\n"));
#endif
			memcpy(target_addr,ip_gateway,4);
		} else {
#ifdef DEBUG_ARP_ROUTING
			serial0_tx_PGM(PSTR("arp: using original address\r\n"));
#endif
			memcpy(target_addr,dst,4); /* safe to just ARP for this */
		}
	} else {
#ifdef DEBUG_ARP_ROUTING
		serial0_tx_PGM(PSTR("arp: no routing, defaulting to original address\r\n"));
#endif
		memcpy(target_addr,dst,4); /* use whatever address we have */
	}

	/* search the current ARP cache for an entry, saving the oldest slot */
	for (n = 0; n < ARP_TABLE_MAX; n++) {
		if (memcmp(target_addr,arp_table[n].addr,4) == 0) {
			/* we have an entry in the table for this! */
			/* touch the age */
			//arp_table[n].age = clock_s;
			/* provide it back */
			if (dst_mac) {
				memcpy(dst_mac,arp_table[n].mac,6);
			}
			/* return E_RESOLVED as go code */
			return E_RESOLVED;
		}
		if (low > ARP_TABLE_MAX) {
			low = n;
		} else {
			if (arp_table[n].age < arp_table[low].age) {
				low = n;
			}
		}
	}

	/* we didn't get a resolution, nuke an entry from the table and use it to store the TX slot plan */
	arp_table[low].age = rtc_s;
	memcpy(arp_table[low].addr,target_addr,4); /* copy address into table */
	memset(arp_table[low].mac,0,6); /* wipe the old MAC in the table */
	arp_table[low].slot = slot+1; /* we have an active packet waiting at slot-1 */

	/* build ARP request */
	{
		arp_ip_pkt_t tx;
		uint8_t n, newslot;

		newslot = encx24j600_tx_freeslot();
		if (!newslot) {
			serial0_tx_PGM(PSTR("arp: unable to issue arp request\r\n"));
			return E_NOTHING;
		}

		tx.hardware[0] = 0x0;
		tx.hardware[1] = 0x1; /* Ethernet */
		tx.protocol[0] = 0x8;
		tx.protocol[1] = 0x0; /* IP */
		tx.hardware_len = 6; /* Ethernet MAC length */
		tx.protocol_len = 4; /* IP address length */
		tx.opcode[0] = 0x0;
		tx.opcode[1] = ARP_REQ; /* This is a request */
		for (n = 0; n < 4; n++) { /* copy IP addresses */
			tx.protocol_target[n] = target_addr[n];
			tx.protocol_sender[n] = ip_addr[n];
		}
		for (n = 0; n < 6; n++) { /* copy MAC addresses */
			tx.hardware_sender[n] = eth_mac[n];
			tx.hardware_target[n] = 0;
		}
		/* prepend to the slot and send unilaterally */

		if (encx24j600_tx_prepend(newslot,(uint8_t *)&tx,sizeof(arp_ip_pkt_t))) {
			uint8_t mac[6] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff}; /* broadcast MAC */
			if (ether_send(mac,0x0806,newslot)) { /* 0x0806 is ARP */
				return E_NEEDS_ARP;
			}
		}
	}
	serial0_tx_PGM(PSTR("arp: tx failure\r\n"));
	/* purge the entry since our TX failed */
	memset(&(arp_table[low]),0,sizeof(arp_table_entry_t));
	return E_NOTHING;
}

void arp_tick(void) {
	uint8_t n;
	/* if we have anything older than the current clock with pending transmits, just purge it. 1s is enough to get arp response. */
	for (n = 0; n < ARP_TABLE_MAX; n++) {
		if (arp_table[n].age && rtc_s-arp_table[n].age > ARP_PENDING && arp_table[n].slot) {
			/* purge it */
#ifdef DEBUG_ARP
			serial0_tx_PGM(PSTR("arp: purging entry "));
			serial0_tx_dec(n);
			serial0_tx_PGM(PSTR(" with packet slot "));
			serial0_tx_dec(arp_table[n].slot);
			serial0_tx_cr();
#endif
			encx24j600_slot_done(arp_table[n].slot-1);
			memset(&(arp_table[n]),0,sizeof(arp_table_entry_t));
		}
		if (arp_table[n].age && rtc_s-arp_table[n].age > ARP_AGE) {
#ifdef DEBUG_ARP
			serial0_tx_PGM(PSTR("arp: purging entry "));
			serial0_tx_dec(n);
			serial0_tx_PGM(PSTR(" with packet slot "));
			serial0_tx_dec(arp_table[n].slot);
			serial0_tx_cr();
#endif
			/* purge entries older than 5 minutes, force re-arp */
			if (arp_table[n].slot) {
				encx24j600_slot_done(arp_table[n].slot-1);
			}
			memset(&(arp_table[n]),0,sizeof(arp_table_entry_t));
		}
	}
}
